package world.ztomorrow.search.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;
import world.ztomorrow.common.dto.PageRespDto;
import world.ztomorrow.common.dto.RestResp;
import world.ztomorrow.search.dto.req.BookSearchReqDto;
import world.ztomorrow.search.dto.resp.BookInfoRespDto;
import world.ztomorrow.search.service.SearchService;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static world.ztomorrow.search.constant.SearchConstant.*;


@Service
@RequiredArgsConstructor
public class EsSearchServiceImpl implements SearchService {

    private final RestHighLevelClient client;
    private final Map<String, String> map = Map.of(
            "last_chapter_update_time", LAST_CHAPTER_UPDATE_TIME,
            "word_count", WORD_COUNT,
            "visit_count", VISIT_COUNT
    );

    @SneakyThrows
    @Override
    public RestResp<PageRespDto<BookInfoRespDto>> searchBooks(BookSearchReqDto query)  {
        // 通过es查询书籍
        SearchRequest request = new SearchRequest(INDEX_BOOK);
        BoolQueryBuilder bool = new BoolQueryBuilder();
        // 关键词全文检索查询，参与排序算分
        if (!StrUtil.isEmptyIfStr(query.getKeyword())) {
            bool.must(QueryBuilders.multiMatchQuery(
                    query.getKeyword(),
                    BOOK_NAME, AUTHOR_NAME, LAST_CHAPTER_NAME));
        }
        // workDirection categoryId bookStatus 精确查询，不参与算分
        if (!StrUtil.isEmptyIfStr(query.getWorkDirection())) {
            bool.filter(QueryBuilders.termQuery(WORK_DIRECTION, query.getWorkDirection()));
        }
        if (!StrUtil.isEmptyIfStr(query.getCategoryId())) {
            bool.filter(QueryBuilders.termQuery(CATEGORY_ID, query.getCategoryId()));
        }
        if (!StrUtil.isEmptyIfStr(query.getBookStatus())) {
            bool.filter(QueryBuilders.termQuery(BOOK_STATUS, query.getBookStatus()));
        }
        // wordCount lastChapterUpdateTime 范围查询 不参与算分
        if (!StrUtil.isEmptyIfStr(query.getWordCountMax())
                && !StrUtil.isEmptyIfStr(query.getWordCountMin())) {
            bool.filter(QueryBuilders.rangeQuery(WORD_COUNT)
                    .gt(query.getWordCountMin()).lt(query.getWordCountMax()));
        }
        if (!StrUtil.isEmptyIfStr(query.getUpdateTimeMin())) {
            bool.filter(QueryBuilders.rangeQuery(LAST_CHAPTER_UPDATE_TIME)
                    .gte(query.getUpdateTimeMin().getTime()));
        }
        request.source().query(bool);
        // lastChapterUpdateTime wordCount visitCount 排序
        if (query.getSort() != null) {
            String key = query.getSort().split(" ")[0];
            String order = query.getSort().split(" ")[1];
            request.source().sort(map.get(key), order.equals("desc") ? SortOrder.DESC : SortOrder.ASC);
        }
        // 分页
        request.source().from((query.getPageNum() - 1) * query.getPageSize())
                .size(query.getPageSize());
        // 匹配字体高亮显示
        request.source().highlighter(SearchSourceBuilder.highlight()
                .field(BOOK_NAME)
                .field(AUTHOR_NAME)
                .field(LAST_CHAPTER_NAME)
                .preTags("<em style='color:red'>")
                .postTags("</em>"));

        // 提交条件发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        SearchHits sh = response.getHits();
        long total = sh.getTotalHits().value;
        SearchHit[] hits =  sh.getHits();
        List<BookInfoRespDto> res = new ArrayList<>();
        for (SearchHit hit : hits) {
            String source = hit.getSourceAsString();
            BookInfoRespDto bookInfoRespDto = JSONUtil.toBean(source, BookInfoRespDto.class);

            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (!highlightFields.isEmpty()) {
                if (highlightFields.get(BOOK_NAME) != null) {
                    bookInfoRespDto.setBookName(highlightFields
                            .get(BOOK_NAME).getFragments()[0].string());
                }
                if (highlightFields.get(AUTHOR_NAME) != null) {
                    bookInfoRespDto.setAuthorName(highlightFields
                            .get(AUTHOR_NAME).getFragments()[0].string());
                }
                if (highlightFields.get(LAST_CHAPTER_NAME) != null) {
                    bookInfoRespDto.setBookDesc(highlightFields
                            .get(LAST_CHAPTER_NAME).getFragments()[0].string());
                }
            }

            res.add(bookInfoRespDto);
        }
//        client.close();
        return RestResp.ok(PageRespDto.of(
                query.getPageNum(), query.getPageSize(),
                total, res));
    }
}
